# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Classes and objects for the Servo Client API.
"""

import re
import xmlrpclib

DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 9999


class ServoClientError(Exception):
  """Error class for ServoRequest"""
  def __init__(self, text, xmlexc):
    """Constructor for ServoClientError Class

    This class wraps xmlrpclib.Fault exceptions.

    Args:
      text: a string, error message generated by caller of exception handler
      xmlexc: xmlrpclib.Fault object supplied by the caught exception. In some
              cases is replaced with None and ignored.

    xmlrpclib.Fault.faultString has the following format:

    <type 'exception type'>:'actual error message'

    we filter out the actual error message to make it available for the
    downstream exception handler
    """
    if xmlexc:
      xml_error = re.sub('^.*>:', '', xmlexc.faultString)
      err_match = re.match('No control named (\w+)', xml_error)
      if err_match:
        name = err_match.group(1)
        error_msg = 'No control named "%s"\n' % name
        # We know that the second line of the fault text is the comma
        # separated list of all available controls. Let's try finding
        # something similar to what user requested.
        all_controls = xml_error.splitlines()[1]
        candidates = [x for x in all_controls.split(',') if name in x]
        if candidates:
          error_msg += "Consider %s" % ' '.join(candidates)
      else:
        error_msg = xml_error
      self.message = '%s :: %s' % (text, error_msg)
    else:
      self.message = text


class ServoClient(object):
  """Class to link client to servod via xmlrpc.

  Beyond method initialize, the remaining methods (doc_all, doc, get, get_all,
  set) have a corresponding method implmented in servod's server.
  """
  def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT, verbose=False):
    """Constructor for ServoClient Class

    Args:
      host: name or IP address of servo server host
      port: TCP port on which servod is listening on
      verbose: enable verbose messaging across xmlrpclib.ServerProxy
    """
    self._verbose = verbose
    remote = 'http://%s:%s' % (host, port)
    self._server = xmlrpclib.ServerProxy(remote, verbose=self._verbose,
                                         allow_none=True)

  def doc_all(self):
    """Get the doc string for all controls from servo.

    Returns:
     string of all doc strings of controls from servo.
    """
    return self._server.doc_all()

  def doc(self, name):
    """Get the doc string from servo for control name.

    Args:
      name: string, name of control to retrieve doc string for.

    Returns:
     doc string for control name

    Raises:
      ServoClientError: If error occurs retrieving doc string.
    """
    try:
      return self._server.doc(name)
    except  xmlrpclib.Fault as e:
      raise ServoClientError("Problem docstring '%s'" % name, e)

  def get(self, name):
    """Get the value from servo for control name.

    Args:
      name: string, name of control to get value for.

    Returns:
     value currently set on control name

    Raises:
      ServoClientError: If error occurs getting value.
    """
    try:
      return self._server.get(name)
    except xmlrpclib.Fault as e:
      raise ServoClientError("Problem getting '%s'" % name, e)

  def get_all(self):
    """Get all controls current values.

    Returns:
      String of all controls and their current values
    """
    return self._server.get_all(self._verbose)

  def set_get_all(self, controls):
    """Set &| get one or more control values.

    Args:
      controls: string, controls to set &| get.

    Raises:
      ServoClientError: If error occurs setting value.
    """
    try:
      rv = self._server.set_get_all(controls)
    except xmlrpclib.Fault as e:
      # TODO(tbroch) : more detail of failure.  Note xmlrpclib only
      #                passes one exception above
      raise ServoClientError("Problem with %s" % (controls), e)
    return rv

  def set(self, name, value):
    """Set the value from servo for control name.

    Args:
      name: string, name of control to set.
      value: string, value to set control to.

    Raises:
      ServoClientError: If error occurs setting value.
    """
    try:
      self._server.set(name, value)
    except xmlrpclib.Fault as e:
      # TODO(tbroch) : more detail of failure.  Note xmlrpclib only
      #                passes one exception above
      raise ServoClientError("Problem setting '%s' to '%s'" %
                              (name, value), e)

  def hwinit(self):
    """Initialize the controls."""
    self._server.hwinit()
